package git.soulbgm.utils;

import io.jsonwebtoken.*;
import io.jsonwebtoken.impl.crypto.MacProvider;
import lombok.extern.slf4j.Slf4j;
import org.apache.tomcat.util.codec.binary.Base64;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * JWT校验工具类
 * iss: jwt签发者
 * sub: jwt所面向的用户
 * aud: 接收jwt的一方
 * exp: jwt的过期时间，这个过期时间必须要大于签发时间
 * nbf: 定义在什么时间之前，该jwt都是不可用的
 * iat: jwt的签发时间
 * jti: jwt的唯一身份标识，主要用来作为一次性token,从而回避重放攻击
 * <p>
 * 一个JWT实际上就是一个字符串，它由三部分组成，头部、载荷与签名。
 * 在JWT中，不应该在载荷里面加入任何敏感的数据
 * http://blog.leapoahead.com/2015/09/06/understanding-jwt/
 *
 * @author SoulBGM
 * @version 1.0
 * @date 2019/10/31 9:51
 */
@Slf4j
public class JwtUtil {

    /**
     * JWT加解密类型
     */
    private static final SignatureAlgorithm JWT_ALG = SignatureAlgorithm.HS256;

    /**
     * JWT生成密钥使用的密码
     */
    private static String JWT_RULE = "git.soulbgm";

    /**
     * JWT添加至http head中的前缀
     */
    private static final String JWT_SEPARATOR = "Bearer ";

    public static void setJwtRule(String jwtRule) {
        JWT_RULE = jwtRule;
    }

    /**
     * 使用JWT默认方式，生成加解密密钥
     *
     * @param alg 加解密类型
     * @return 密钥
     */
    public static SecretKey generateKey(SignatureAlgorithm alg) {
        return MacProvider.generateKey(alg);
    }

    /**
     * 使用指定密钥生成规则，生成JWT加解密密钥
     *
     * @param alg  加解密类型
     * @param rule 密钥生成规则
     * @return 密钥
     */
    public static SecretKey generateKey(SignatureAlgorithm alg, String rule) {
        // 将密钥生成键转换为字节数组
        byte[] bytes = Base64.decodeBase64(rule);
        return new SecretKeySpec(bytes, alg.getJcaName());
    }

    /**
     * 通过调用generateKey(JWT_ALG, JWT_RULE)获得默认Key
     *
     * @return 加密密钥
     */
    public static Key getDefaultKey() {
        return generateKey(JWT_ALG, JWT_RULE);
    }

    /**
     * 构建JWT
     *
     * @param map      自定义载荷字段
     * @param duration 有效时间，单位：秒
     * @return JWT字符串
     */
    public static String buildJwt(Map<String, Object> map, Integer duration) {
        return buildJwt(JWT_ALG, getDefaultKey(), null, null, null, null, null, duration, map);
    }

    /**
     * 构建JWT
     *
     * @param alg      加密算法
     * @param key      加密密钥
     * @param sub      面向的用户
     * @param aud      接收方
     * @param jti      唯一身份标识
     * @param iss      签发者
     * @param nbf      生效日期时间
     * @param duration 有效时间，单位：秒
     * @param map      自定义载荷字段
     * @return JWT字符串
     */
    public static String buildJwt(SignatureAlgorithm alg, Key key, String sub, String aud, String jti, String iss, Date nbf, Integer duration, Map<String, Object> map) {
        // jwt签发时间
        Date iat = new Date();
        // jwt的过期时间，这个过期时间必须大于签发时间
        Date exp = null;
        if (duration != null) {
            exp = nbf == null ? new Date(iat.getTime() + duration * 1000) : new Date(nbf.getTime() + duration * 1000);
        }

        Claims claims = Jwts.claims()
                .setSubject(sub)
                .setAudience(aud)
                .setId(jti)
                .setIssuer(iss)
                .setNotBefore(nbf)
                .setIssuedAt(iat)
                .setExpiration(exp);

        if (map != null) {
            claims.putAll(map);
        }

        String compact = Jwts.builder()
                .signWith(alg, key)
                .setClaims(claims)
                .compact();

        // 在JWT字符串前添加"Bearer "字符串，用于加入"Authorization"请求头
        return JWT_SEPARATOR + compact;
    }

    /**
     * 获得JWT主体
     *
     * @param claimsJws jwt内容文本
     * @return Map对象
     */
    public static Map<String, Object> getBody(String claimsJws) {
        Jws<Claims> jws = parseJwt(claimsJws);
        return jws.getBody();
    }

    /**
     * 解析JWT
     *
     * @param claimsJws jwt内容文本
     * @return Jws对象
     */
    public static Jws<Claims> parseJwt(String claimsJws) {
        return parseJwt(getDefaultKey(), claimsJws);
    }

    /**
     * 解析JWT
     *
     * @param key       加密密钥
     * @param claimsJws jwt内容文本
     * @return Jws对象
     */
    public static Jws<Claims> parseJwt(Key key, String claimsJws) {
        claimsJws = claimsJws.substring(JWT_SEPARATOR.length());
        try {
            return Jwts.parser().setSigningKey(key).parseClaimsJws(claimsJws);
        } catch (ExpiredJwtException e) {
            log.warn("Jwt已过期, {}", e.getMessage());
            throw e;
        } catch (UnsupportedJwtException e) {
            log.warn("不支持的Jwt, {}", e.getMessage());
            throw e;
        } catch (MalformedJwtException e) {
            log.warn("Jwt格式错误, {}", e.getMessage());
            throw e;
        } catch (SignatureException e) {
            log.warn("Jwt签名异常, {}", e.getMessage());
            throw e;
        } catch (IllegalArgumentException e) {
            log.warn("非法论点, {}", e.getMessage());
            throw e;
        }
    }

    /**
     * 校验JWT
     *
     * @param claimsJws Jwt内容文本
     * @return true or false
     */
    public static boolean checkJwt(String claimsJws) {
        return checkJwt(getDefaultKey(), claimsJws);
    }

    /**
     * 校验JWT
     *
     * @param key       加密密钥
     * @param claimsJws Jwt内容文本
     * @return true or false
     */
    public static boolean checkJwt(Key key, String claimsJws) {
        boolean flag = false;
        try {
            Claims claims = parseJwt(key, claimsJws).getBody();
            flag = claims != null;
        } catch (Exception e) {
            log.warn("JWT验证出错，错误原因:{}", e.getMessage());
        }
        return flag;
    }

    public static void main(String[] args) {
//        SecretKey secretKey = generateKey(SignatureAlgorithm.HS384);
//        System.out.println(secretKey);
        Map<String, Object> map = new HashMap<>(2);
        map.put("username", "zhangsan");
        map.put("userId", "sdfskk1jkj3k12k31k");
        SecretKey key = generateKey(JWT_ALG, JWT_RULE);
//        String jwt = buildJwt(JWT_ALG, key, "SoulBGM", "SoulBGM", UUID.randomUUID().toString(), "SoulBGM", new Date(), 60, map);
        String jwt = buildJwt(map, 60);
        System.out.println(jwt);
//        "Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ7fSIsImp0aSI6IjViMmJhNzU4LTViYzYtNDIwNS04NzJmLTM2MzZlZjUyMmUzMyIsImV4cCI6MTU3MjUwMDU0OX0.UgZY4Zo-TALhpdUT2AxsZdvlrsQwjsXS8d-l995TVyo"
//        Jws<Claims> jws = parseJwt(key, jwt);
        Jws<Claims> jws = parseJwt(jwt);
        System.out.println(jws);
        System.out.println(jws.getSignature());
        System.out.println(jws.getBody().getId());
        System.out.println(jws.getBody().getSubject());
        Date expiration = jws.getBody().getExpiration();
        System.out.println(DateUtil.format(expiration, DateUtil.FORMAT_YMD_HMS));
        System.out.println(jws.getBody().get("userId", String.class));
        System.out.println(jws.getBody().get("username", String.class));
        System.out.println(jws.getHeader());

//        System.out.println(checkJwt(key, jwt));
        System.out.println(checkJwt(jwt));
    }

}
